package mas;

import java.util.ArrayList;

import rinde.sim.core.graph.Point;

public class Map{

	private double mapWidth,mapHeigth,xOffset,yOffset;
	private int nbZones;
	private ArrayList<Zone> zones;
	
	public double getMapWidth() {
		return mapWidth;
	}

	public void setMapWidth(double mapWidth) {
		this.mapWidth = mapWidth;
	}

	public double getMapHeigth() {
		return mapHeigth;
	}

	public void setMapHeigth(double mapHeigth) {
		this.mapHeigth = mapHeigth;
	}

	public int getNbZones() {
		return nbZones;
	}

	public void setNbZones(int nbZones) {
		this.nbZones = nbZones;
	}
	
	public static void main(String[] args) {
		for(int i=1;i<=8;i++){
			Map theMap = new Map(5,5,10.0, 10.0, i);
			theMap.divideIntoZones(i);
		}
	}
	
	public Map(double xOffset, double yOffset, double width, double heigth, int nbZones){
		this.xOffset=xOffset;
		this.yOffset=yOffset;
		this.mapWidth=width;
		this.mapHeigth=heigth;
		this.nbZones= nbZones;
		this.zones=divideIntoZones(nbZones);
	}
	
	// returns a list with zones,
	// they cover the entire area and divide it into a number of zones, corresponding to the parameter
	private ArrayList<Zone> divideIntoZones(int nbZones){
		ArrayList<Zone> zones = new ArrayList<Zone>();
		ArrayList<Integer> numbers = findOptimalDivisionNumbers(nbZones);
		double currentX = xOffset;
		double currentY = yOffset;
		int counter = 1;
		for(int i: numbers){
			double zoneWidth =  mapWidth / ((double) i);
			double zoneHeigth = ((mapHeigth/(double)nbZones))*((double) i);
			for(int j=1;j<=i;j++){
				Point bottomLeft = new Point(currentX, currentY);
				double endX = currentX+zoneWidth;
				double endY = currentY+zoneHeigth;
				Point topRight = new Point(endX,endY);
				// only useful for testing
				 //System.out.println("Zone from (" + currentX + "," + currentY + ") to (" + 
					//				endX + "," + endY + ")");
				zones.add(new Zone(bottomLeft, topRight, counter));
				counter ++;
				currentX += zoneWidth;
			}
			currentY += zoneHeigth;
			currentX = xOffset;
		}
		return zones;
	}

	// returns a list with numbers: each numbers represents the number of zones that should be in the
	// next row of zones
	// this is an algorithm for dividing the map in equally sized zones that are as square as possible
	private static ArrayList<Integer> findOptimalDivisionNumbers(int nbZones) {
		ArrayList<Integer> divisionNumbers = new ArrayList<Integer>();
		double root = Math.sqrt(nbZones);
		int nbGroups = (int) Math.round(root);
		int nbGroupsWithHigherNumber = nbZones % nbGroups;
		int nbGroupsWithLowerNumber = nbGroups - nbGroupsWithHigherNumber;
		int higherNumber = (int) Math.ceil(((double)nbZones)/((double) nbGroups));
		int lowerNumber = (int) Math.floor(((double)nbZones)/((double) nbGroups));
		for(int i = 0;i<nbGroupsWithHigherNumber;i++){
			divisionNumbers.add(higherNumber);
		}
		for(int j = 0;j<nbGroupsWithLowerNumber;j++){
			divisionNumbers.add(lowerNumber);
		}
		return divisionNumbers;
	}

	public ArrayList<Zone> getZones() {
		return zones;
	}
	
	//adds the box to the zone in which it is supposed to be put
	public void addBox(Box box){
		Zone zoneToPutBoxIn = this.getZoneGivenZoneNumber(box.getPickupZoneNumber());
		zoneToPutBoxIn.addBox(box);
	}
	
	public Zone getZoneGivenZoneNumber(int number){
		for(Zone zone: zones){
			if(zone.getZoneNumber()==number){
				return zone;
			}
		}
		return null;
	}

	public void setZones(ArrayList<Zone> zones) {
		this.zones = zones;
	}
	
	public Zone getZoneGivenPoint(Point point) throws Exception{
		for(Zone zone: zones){
			if(zone.containsPoint(point)){
				return zone;
			}
		}
		throw new Exception("Point not on map");
	}

	public int getZoneNumberGivenPoint(Point point) throws Exception{
		for(Zone zone: zones){
			if(zone.containsPoint(point)){
				return zone.getZoneNumber();
			}
		}
		throw new Exception("Point not on map");
	}
}
